﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Windows.Forms.Design.Behavior;
using Microsoft.Win32;

namespace System.Windows.Forms.Design;

/// <summary>
///  Provides the component tray UI for the form designer.
/// </summary>
[ToolboxItem(false)]
[DesignTimeVisible(false)]
[ProvideProperty("Location", typeof(IComponent))]
[ProvideProperty("TrayLocation", typeof(IComponent))]
public class ComponentTray : ScrollableControl, IExtenderProvider, ISelectionUIHandler, IOleDragClient
{
    private static Point InvalidPoint { get; } = new(int.MinValue, int.MinValue);
    private IServiceProvider _serviceProvider; // Where services come from.
    private Point _whiteSpace = Point.Empty; // space to leave between components.
    private Size _grabHandle = Size.Empty; // Size of the grab handles.

    private List<Control> _controls; // List of items in the tray in the order of their layout.
    private SelectionUIHandler _dragHandler; // the thing responsible for handling mouse drags
    private ISelectionUIService _selectionUISvc; // selection UI; we use this a lot
    private IToolboxService _toolboxService; // cached for drag/drop

    /// <summary>
    ///  Provides drag and drop functionality through OLE.
    /// </summary>
    internal OleDragDropHandler _oleDragDropHandler; // handler class for ole drag drop operations.

    private readonly IDesigner _mainDesigner; // the designer that is associated with this tray
    private IEventHandlerService _eventHandlerService; // Event Handler service to handle keyboard and focus.
    private bool _queriedTabOrder;
    private MenuCommand _tabOrderCommand;
    private ICollection _selectedObjects;

    // Services that we use on a high enough frequency to merit caching.
    private IMenuCommandService _menuCommandService;
    private InheritanceUI _inheritanceUI;

    private Point _mouseDragStart = InvalidPoint; // the starting location of a drag
    private Point _mouseDragEnd = InvalidPoint; // the ending location of a drag
    private Rectangle _mouseDragWorkspace = Rectangle.Empty; // a temp work rectangle we cache for perf
    private ToolboxItem _mouseDragTool; // the tool that's being dragged; only for drag/drop
    private Point _mouseDropLocation = InvalidPoint; // where the tool was dropped
    private bool _showLargeIcons; // Show Large icons or not.
    private bool _autoArrange; // allows for auto arranging icons.
    private Point _autoScrollPosBeforeDragging = Point.Empty; // Used to return the correct scroll pos. after a drag

    // Component Tray Context menu items...
    private readonly MenuCommand _menucmdArrangeIcons;
    private readonly MenuCommand _menucmdLineupIcons;
    private readonly MenuCommand _menucmdLargeIcons;
    private bool _fResetAmbient;
    private bool _fSelectionChanged;
    private ComponentTrayGlyphManager _glyphManager; // used to manage any glyphs added to the tray

    // Empty class for build time dependancy

    /// <summary>
    ///  Creates a new component tray. The component tray
    ///  will monitor component additions and removals and create
    ///  appropriate UI objects in its space.
    /// </summary>
    public ComponentTray(IDesigner mainDesigner, IServiceProvider serviceProvider)
    {
        AutoScroll = true;
        _mainDesigner = mainDesigner;
        _serviceProvider = serviceProvider;
        AllowDrop = true;
        Text = "ComponentTray"; // makes debugging easier
        SetStyle(ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);
        _controls = [];
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        IExtenderProviderService es = (IExtenderProviderService)GetService(typeof(IExtenderProviderService));
        Debug.Assert(es is not null, "Component tray wants an extender provider service, but there isn't one.");
        es?.AddExtenderProvider(this);

        if (GetService(typeof(IEventHandlerService)) is null)
        {
            if (host is not null)
            {
                _eventHandlerService = new EventHandlerService(this);
                host.AddService(_eventHandlerService);
            }
        }

        IMenuCommandService mcs = MenuService;
        if (mcs is not null)
        {
            Debug.Assert(_menucmdArrangeIcons is null, "Non-Null Menu Command for ArrangeIcons");
            Debug.Assert(_menucmdLineupIcons is null, "Non-Null Menu Command for LineupIcons");
            Debug.Assert(_menucmdLargeIcons is null, "Non-Null Menu Command for LargeIcons");
            _menucmdArrangeIcons = new MenuCommand(OnMenuArrangeIcons, StandardCommands.ArrangeIcons);
            _menucmdLineupIcons = new MenuCommand(OnMenuLineupIcons, StandardCommands.LineupIcons);
            _menucmdLargeIcons = new MenuCommand(OnMenuShowLargeIcons, StandardCommands.ShowLargeIcons);
            _menucmdArrangeIcons.Checked = AutoArrange;
            _menucmdLargeIcons.Checked = ShowLargeIcons;
            mcs.AddCommand(_menucmdArrangeIcons);
            mcs.AddCommand(_menucmdLineupIcons);
            mcs.AddCommand(_menucmdLargeIcons);
        }

        IComponentChangeService componentChangeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));
        if (componentChangeService is not null)
        {
            componentChangeService.ComponentRemoved += OnComponentRemoved;
        }

        if (GetService(typeof(IUIService)) is IUIService uiService)
        {
            Color styleColor;
            if (uiService.Styles["ArtboardBackground"] is Color backgroundColor)
            {
                styleColor = backgroundColor;
            }

            // Can't use 'as' here since Color is a value type
            else if (uiService.Styles["VsColorDesignerTray"] is Color designerTrayColor)
            {
                styleColor = designerTrayColor;
            }
            else if (uiService.Styles["HighlightColor"] is Color highlightColor)
            {
                // Since v1, we have had code here that checks for HighlightColor,
                // so some hosts (like WinRes) have been setting it.
                // If VsColorDesignerTray isn't present, we look for HighlightColor for backward compatibility.
                styleColor = highlightColor;
            }
            else
            {
                // No style color provided? Let's pick a default.
                styleColor = SystemColors.Info;
            }

            if (uiService.Styles["ArtboardBackgroundText"] is Color backgroundTextColor)
            {
                ForeColor = backgroundTextColor;
            }
            else if (uiService.Styles["VsColorPanelText"] is Color panelTextColor)
            {
                ForeColor = panelTextColor;
            }

            BackColor = styleColor;
            Font = (Font)uiService.Styles["DialogFont"];
        }

        ISelectionService selSvc = (ISelectionService)GetService(typeof(ISelectionService));
        if (selSvc is not null)
        {
            selSvc.SelectionChanged += OnSelectionChanged;
        }

        // Listen to the SystemEvents so that we can resync selection based on display settings etc.
        SystemEvents.DisplaySettingsChanged += OnSystemSettingChanged;
        SystemEvents.InstalledFontsChanged += OnSystemSettingChanged;
        SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;

        if (GetService(typeof(BehaviorService)) is BehaviorService behSvc)
        {
            // this object will manage any glyphs that get added to our tray
            _glyphManager = new ComponentTrayGlyphManager(behSvc);
        }
    }

    private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
    {
        if (IsHandleCreated)
        {
            _fResetAmbient = true;
            ResetTrayControls();
            BeginInvoke(new AsyncInvokeHandler(Invalidate), [true]);
        }
    }

    private void OnComponentRefresh(RefreshEventArgs e)
    {
        if (e.ComponentChanged is IComponent component)
        {
            TrayControl control = TrayControl.FromComponent(component);
            if (control is not null)
            {
                bool shouldDisplay = CanDisplayComponent(component);
                if (shouldDisplay != control.Visible || !shouldDisplay)
                {
                    control.Visible = shouldDisplay;
                    Rectangle bounds = control.Bounds;
                    bounds.Inflate(_grabHandle);
                    bounds.Inflate(_grabHandle);
                    Invalidate(bounds);
                    PerformLayout();
                }
            }
        }
    }

    private void OnSystemSettingChanged(object sender, EventArgs e)
    {
        if (IsHandleCreated)
        {
            _fResetAmbient = true;
            ResetTrayControls();
            BeginInvoke(new AsyncInvokeHandler(Invalidate), [true]);
        }
    }

    private void ResetTrayControls()
    {
        ControlCollection children = Controls;
        if (children is null)
        {
            return;
        }

        for (int i = 0; i < children.Count; ++i)
        {
            if (children[i] is TrayControl tc)
            {
                tc._fRecompute = true;
            }
        }
    }

    private delegate void AsyncInvokeHandler(bool children);

    private void OnSelectionChanged(object sender, EventArgs e)
    {
        _selectedObjects = ((ISelectionService)sender).GetSelectedComponents();
        object primary = ((ISelectionService)sender).PrimarySelection;
        Invalidate();
        _fSelectionChanged = true;

        // Accessibility information
        foreach (object selObj in _selectedObjects)
        {
            if (selObj is IComponent component)
            {
                Control c = TrayControl.FromComponent(component);
                if (c is not null)
                {
                    PInvoke.NotifyWinEvent(
                        (uint)AccessibleEvents.SelectionAdd,
                        c,
                        (int)OBJECT_IDENTIFIER.OBJID_CLIENT,
                        (int)PInvoke.CHILDID_SELF);
                }
            }
        }

        if (primary is IComponent comp)
        {
            Control c = TrayControl.FromComponent(comp);
            if (c is not null && IsHandleCreated)
            {
                ScrollControlIntoView(c);
                PInvoke.NotifyWinEvent(
                    (uint)AccessibleEvents.Focus,
                    c,
                    (int)OBJECT_IDENTIFIER.OBJID_CLIENT,
                    (int)PInvoke.CHILDID_SELF);
            }

            if (_glyphManager is not null)
            {
                _glyphManager.SelectionGlyphs.Clear();
                IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
                foreach (object selObj in _selectedObjects)
                {
                    if (selObj is IComponent selectedComponent && !(host.GetDesigner(selectedComponent) is ControlDesigner))
                    { // don't want to do it for controls that are also in the tray
                        GlyphCollection glyphs = _glyphManager.GetGlyphsForComponent(selectedComponent);
                        if (glyphs is not null && glyphs.Count > 0)
                        {
                            SelectionGlyphs.AddRange(glyphs);
                        }
                    }
                }
            }
        }
    }

    private void OnComponentRemoved(object sender, ComponentEventArgs cevent)
    {
        RemoveComponent(cevent.Component);
    }

    private void OnMenuShowLargeIcons(object sender, EventArgs e)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        DesignerTransaction t = null;
        try
        {
            t = host.CreateTransaction(SR.TrayShowLargeIcons);
            PropertyDescriptor trayIconProp = TypeDescriptor.GetProperties(_mainDesigner.Component)["TrayLargeIcon"];
            trayIconProp?.SetValue(_mainDesigner.Component, !ShowLargeIcons);
        }
        finally
        {
            t?.Commit();
        }
    }

    private void OnMenuLineupIcons(object sender, EventArgs e)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        DesignerTransaction t = null;
        try
        {
            t = host.CreateTransaction(SR.TrayLineUpIcons);
            DoLineupIcons();
        }
        finally
        {
            t?.Commit();
        }
    }

    private void DoLineupIcons()
    {
        if (_autoArrange)
        {
            return;
        }

        bool oldValue = _autoArrange;
        _autoArrange = true;
        try
        {
            DoAutoArrange(true);
        }
        finally
        {
            _autoArrange = oldValue;
        }
    }

    private void DoAutoArrange(bool dirtyDesigner)
    {
        if (_controls is null || _controls.Count <= 0)
        {
            return;
        }

        _controls.Sort(new AutoArrangeComparer());

        SuspendLayout();

        // Reset the autoscroll position before auto arranging.
        // This way, when OnLayout gets fired after this, we won't
        // have to move every component again. Note that syncing
        // the selection will automatically select & scroll into view
        // the right components
        AutoScrollPosition = new Point(0, 0);

        try
        {
            Control prevCtl = null;
            bool positionedGlobal = true;
            foreach (Control ctl in _controls)
            {
                if (!ctl.Visible)
                {
                    continue;
                }

                // If we're auto arranging, always move the control. If not, move the control only
                // if it was never given a position. This auto arranges it until the user messes with it,
                // or until its position is saved into the resx. (if one control is no longer positioned,
                // move all the other one as we don't want them to go under one another)
                if (_autoArrange)
                {
                    PositionInNextAutoSlot(ctl as TrayControl, prevCtl, dirtyDesigner);
                }
                else if (!((TrayControl)ctl).Positioned || !positionedGlobal)
                {
                    PositionInNextAutoSlot(ctl as TrayControl, prevCtl, false);
                    positionedGlobal = false;
                }

                prevCtl = ctl;
            }

            _selectionUISvc?.SyncSelection();
        }
        finally
        {
            ResumeLayout();
        }
    }

    private void OnMenuArrangeIcons(object sender, EventArgs e)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        DesignerTransaction t = null;

        try
        {
            t = host.CreateTransaction(SR.TrayAutoArrange);

            PropertyDescriptor trayAAProp = TypeDescriptor.GetProperties(_mainDesigner.Component)["TrayAutoArrange"];
            trayAAProp?.SetValue(_mainDesigner.Component, !AutoArrange);
        }
        finally
        {
            t?.Commit();
        }
    }

    public bool AutoArrange
    {
        get => _autoArrange;
        set
        {
            if (_autoArrange != value)
            {
                _autoArrange = value;
                _menucmdArrangeIcons.Checked = value;

                if (_autoArrange)
                {
                    DoAutoArrange(true);
                }
            }
        }
    }

    /// <summary>
    ///  Gets the number of components contained within this tray.
    /// </summary>
    public int ComponentCount
    {
        get => Controls.Count;
    }

    internal GlyphCollection SelectionGlyphs
    {
        get
        {
            if (_glyphManager is not null)
            {
                return _glyphManager.SelectionGlyphs;
            }
            else
            {
                return null;
            }
        }
    }

    /// <summary>
    ///  Determines whether the tray will show large icon view or not.
    /// </summary>
    public bool ShowLargeIcons
    {
        get => _showLargeIcons;
        set
        {
            if (_showLargeIcons != value)
            {
                _showLargeIcons = value;
                _menucmdLargeIcons.Checked = ShowLargeIcons;

                ResetTrayControls();
                Invalidate(true);
            }
        }
    }

    bool IExtenderProvider.CanExtend(object extendee)
    {
        return (extendee is IComponent comp) && (TrayControl.FromComponent(comp) is not null);
    }

    IComponent IOleDragClient.Component
    {
        get => _mainDesigner.Component;
    }

    bool IOleDragClient.CanModifyComponents
    {
        get => true;
    }

    bool IOleDragClient.AddComponent(IComponent component, string name, bool firstAdd)
    {
        // the designer for controls decides what to do here
        if (_mainDesigner is IOleDragClient oleDragClient)
        {
            try
            {
                oleDragClient.AddComponent(component, name, firstAdd);
                PositionControl(TrayControl.FromComponent(component));
                _mouseDropLocation = InvalidPoint;
                return true;
            }
            catch
            {
            }
        }
        else
        {
            // for webforms (98109) just add the component directly to the host
            IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
            try
            {
                if (host is not null && host.Container is not null)
                {
                    if (host.Container.Components[name] is not null)
                    {
                        name = null;
                    }

                    host.Container.Add(component, name);
                    return true;
                }
            }
            catch
            {
            }
        }

        Debug.Fail("Don't know how to add component!");
        return false;
    }

    bool IOleDragClient.IsDropOk(IComponent component) => true;

    Control IOleDragClient.GetDesignerControl() => this;

    Control IOleDragClient.GetControlForComponent(object component)
    {
        if (component is IComponent comp)
        {
            return TrayControl.FromComponent(comp);
        }

        Debug.Fail("component is not IComponent");
        return null;
    }

    bool ISelectionUIHandler.BeginDrag(object[] components, SelectionRules rules, int initialX, int initialY)
    {
        if (TabOrderActive)
        {
            return false;
        }

        bool result = DragHandler.BeginDrag(components, rules, initialX, initialY);
        if (result)
        {
            if (!GetOleDragHandler().DoBeginDrag(components, rules, initialX, initialY))
            {
                return false;
            }
        }

        return result;
    }

    internal virtual OleDragDropHandler GetOleDragHandler()
    {
        _oleDragDropHandler ??= new TrayOleDragDropHandler(DragHandler, _serviceProvider, this);

        return _oleDragDropHandler;
    }

    internal virtual SelectionUIHandler DragHandler
    {
        get
        {
            _dragHandler ??= new TraySelectionUIHandler(this);

            return _dragHandler;
        }
    }

    void ISelectionUIHandler.DragMoved(object[] components, Rectangle offset) => DragHandler.DragMoved(components, offset);

    void ISelectionUIHandler.EndDrag(object[] components, bool cancel)
    {
        DragHandler.EndDrag(components, cancel);
        GetOleDragHandler().DoEndDrag();
        // Here, after the drag is finished and after we have resumed layout,
        // adjust the location of the components we dragged by the scroll offset
        if (!_autoScrollPosBeforeDragging.IsEmpty)
        {
            foreach (IComponent comp in components)
            {
                TrayControl tc = TrayControl.FromComponent(comp);
                if (tc is not null)
                {
                    SetTrayLocation(comp, new Point(tc.Location.X - _autoScrollPosBeforeDragging.X, tc.Location.Y - _autoScrollPosBeforeDragging.Y));
                }
            }

            AutoScrollPosition = new Point(-_autoScrollPosBeforeDragging.X, -_autoScrollPosBeforeDragging.Y);
        }
    }

    // We render the selection UI glyph ourselves.
    Rectangle ISelectionUIHandler.GetComponentBounds(object component) => Rectangle.Empty;

    SelectionRules ISelectionUIHandler.GetComponentRules(object component) => SelectionRules.Visible | SelectionRules.Moveable;

    Rectangle ISelectionUIHandler.GetSelectionClipRect(object component)
    {
        if (IsHandleCreated)
        {
            return RectangleToScreen(ClientRectangle);
        }

        return Rectangle.Empty;
    }

    void ISelectionUIHandler.OnSelectionDoubleClick(IComponent component)
    {
        if (!TabOrderActive)
        {
            if (((IOleDragClient)this).GetControlForComponent(component) is TrayControl tc)
            {
                tc.ViewDefaultEvent(component);
            }
        }
    }

    bool ISelectionUIHandler.QueryBeginDrag(object[] components, SelectionRules rules, int initialX, int initialY)
        => DragHandler.QueryBeginDrag(components);

    void ISelectionUIHandler.ShowContextMenu(IComponent component) => OnContextMenu(MousePosition);

    private void OnContextMenu(Point location)
    {
        if (TabOrderActive)
        {
            return;
        }

        Capture = false;
        IMenuCommandService mcs = MenuService;
        if (mcs is not null)
        {
            Capture = false;
            Cursor.Clip = Rectangle.Empty;
            ISelectionService selectionService = (ISelectionService)GetService(typeof(ISelectionService));
            if (selectionService is not null
                && !(selectionService.SelectionCount == 1 && selectionService.PrimarySelection == _mainDesigner.Component))
            {
                mcs.ShowContextMenu(MenuCommands.TraySelectionMenu, location.X, location.Y);
            }
            else
            {
                mcs.ShowContextMenu(MenuCommands.ComponentTrayMenu, location.X, location.Y);
            }
        }
    }

    void ISelectionUIHandler.OleDragEnter(DragEventArgs de) => GetOleDragHandler().DoOleDragEnter(de);

    void ISelectionUIHandler.OleDragDrop(DragEventArgs de) => GetOleDragHandler().DoOleDragDrop(de);

    void ISelectionUIHandler.OleDragOver(DragEventArgs de) => GetOleDragHandler().DoOleDragOver(de);

    void ISelectionUIHandler.OleDragLeave() => GetOleDragHandler().DoOleDragLeave();

    /// <summary>
    ///  Adds a component to the tray.
    /// </summary>
    public virtual void AddComponent(IComponent component)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        // Ignore components that cannot be added to the tray
        if (!CanDisplayComponent(component))
        {
            return;
        }

        // And designate us as the selection UI handler for the control.
        if (_selectionUISvc is null)
        {
            _selectionUISvc = (ISelectionUIService)GetService(typeof(ISelectionUIService));

            // If there is no selection service, then we will provide our own.
            if (_selectionUISvc is null)
            {
                _selectionUISvc = new SelectionUIService(host);
                host.AddService(_selectionUISvc);
            }

            _grabHandle = _selectionUISvc.GetAdornmentDimensions(AdornmentType.GrabHandle);
        }

        // Create a new instance of a tray control.
        TrayControl trayctl = new(this, component);
        SuspendLayout();
        try
        {
            // Add it to us.
            Controls.Add(trayctl);
            _controls.Add(trayctl);
            // CanExtend can actually be called BEFORE the component is added to the ComponentTray.
            // ToolStrip is such as scenario:
            // 1. Add a timer to the Tray.
            // 2. Add a ToolStrip.
            // 3. ToolStripDesigner.Initialize will be called before ComponentTray.AddComponent,
            //    so the ToolStrip is not yet added to the tray.
            // 4. TooStripDesigner.Initialize calls GetProperties, which causes our CanExtend to be called.
            // 5. CanExtend will return false, since the component has not yet been added.
            // 6. This causes all sorts of badness
            // Fix is to refresh.
            TypeDescriptor.Refresh(component);
            if (host is not null && !host.Loading)
            {
                PositionControl(trayctl);
            }

            _selectionUISvc?.AssignSelectionUIHandler(component, this);

            InheritanceAttribute attr = trayctl.InheritanceAttribute;
            if (attr.InheritanceLevel != InheritanceLevel.NotInherited)
            {
                InheritanceUI iui = InheritanceUI;
                iui?.AddInheritedControl(trayctl, attr.InheritanceLevel);
            }
        }
        finally
        {
            ResumeLayout();
        }

        if (host is not null && !host.Loading)
        {
            ScrollControlIntoView(trayctl);
        }
    }

    [CLSCompliant(false)]
    protected virtual bool CanCreateComponentFromTool(ToolboxItem tool)
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        Debug.Assert(host is not null, "Service object could not provide us with a designer host.");
        // Disallow controls to be added to the component tray.
        Type compType = host.GetType(tool.TypeName);
        if (compType is null)
        {
            return true;
        }

        if (!compType.IsSubclassOf(typeof(Control)))
        {
            return true;
        }

        Type designerType = GetDesignerType(compType, typeof(IDesigner));
        return !(typeof(ControlDesigner).IsAssignableFrom(designerType));
    }

    private Type GetDesignerType(Type t, Type designerBaseType)
    {
        Type designerType = null;
        // Get the set of attributes for this type
        AttributeCollection attributes = TypeDescriptor.GetAttributes(t);
        for (int i = 0; i < attributes.Count; i++)
        {
            if (attributes[i] is DesignerAttribute da)
            {
                Type attributeBaseType = Type.GetType(da.DesignerBaseTypeName);
                if (attributeBaseType is not null && attributeBaseType == designerBaseType)
                {
                    bool foundService = false;
                    ITypeResolutionService tr = (ITypeResolutionService)GetService(typeof(ITypeResolutionService));
                    if (tr is not null)
                    {
                        foundService = true;
                        designerType = tr.GetType(da.DesignerTypeName);
                    }

                    if (!foundService)
                    {
                        designerType = Type.GetType(da.DesignerTypeName);
                    }

                    if (designerType is not null)
                    {
                        break;
                    }
                }
            }
        }

        return designerType;
    }

    /// <summary>
    ///  This method determines if a UI representation for the given component should be provided.
    ///  If it returns true, then the component will get a glyph in the tray area. If it returns
    ///  false, then the component will not actually be added to the tray. The default
    ///  implementation looks for DesignTimeVisibleAttribute.Yes on the component's class.
    /// </summary>
    protected virtual bool CanDisplayComponent(IComponent component)
    {
        return TypeDescriptor.GetAttributes(component).Contains(DesignTimeVisibleAttribute.Yes);
    }

    [CLSCompliant(false)]
    public void CreateComponentFromTool(ToolboxItem tool)
    {
        if (!CanCreateComponentFromTool(tool))
        {
            return;
        }

        // We invoke the drag drop handler for this. This implementation is shared between all designers that create components.
        GetOleDragHandler().CreateTool(tool, null, 0, 0, 0, 0, false, false);
    }

    /// <summary>
    ///  Displays the given exception to the user.
    /// </summary>
    protected void DisplayError(Exception e)
    {
        IUIService uis = (IUIService)GetService(typeof(IUIService));
        if (uis is not null)
        {
            uis.ShowError(e);
        }
        else
        {
            string message = e.Message;
            if (message is null || message.Length == 0)
            {
                message = e.ToString();
            }

            RTLAwareMessageBox.Show(null, message, null, MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, 0);
        }
    }

    /// <summary>
    ///  Disposes of the resources (other than memory) used by the component tray object.
    /// </summary>
    protected override void Dispose(bool disposing)
    {
        if (disposing && _controls is not null)
        {
            IExtenderProviderService es = (IExtenderProviderService)GetService(typeof(IExtenderProviderService));
            es?.RemoveExtenderProvider(this);

            IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
            if (_eventHandlerService is not null)
            {
                if (host is not null)
                {
                    host.RemoveService<IEventHandlerService>();
                    _eventHandlerService = null;
                }
            }

            IComponentChangeService componentChangeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));
            if (componentChangeService is not null)
            {
                componentChangeService.ComponentRemoved -= OnComponentRemoved;
            }

            SystemEvents.DisplaySettingsChanged -= OnSystemSettingChanged;
            SystemEvents.InstalledFontsChanged -= OnSystemSettingChanged;
            SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
            IMenuCommandService mcs = MenuService;
            if (mcs is not null)
            {
                Debug.Assert(_menucmdArrangeIcons is not null, "Null Menu Command for ArrangeIcons");
                Debug.Assert(_menucmdLineupIcons is not null, "Null Menu Command for LineupIcons");
                Debug.Assert(_menucmdLargeIcons is not null, "Null Menu Command for LargeIcons");
                mcs.RemoveCommand(_menucmdArrangeIcons);
                mcs.RemoveCommand(_menucmdLineupIcons);
                mcs.RemoveCommand(_menucmdLargeIcons);
            }

            _selectionUISvc = null;

            _inheritanceUI?.Dispose();
            _inheritanceUI = null;

            _serviceProvider = null;
            _controls.Clear();
            _controls = null;

            _glyphManager?.Dispose();
            _glyphManager = null;
        }

        base.Dispose(disposing);
    }

    /// <summary>
    ///  Similar to GetNextControl on Control, this method returns the next
    ///  component in the tray, given a starting component. It will return
    ///  null if the end (or beginning, if forward is false) of the list
    ///  is encountered.
    /// </summary>
    public IComponent GetNextComponent(IComponent component, bool forward)
    {
        for (int i = 0; i < _controls.Count; i++)
        {
            TrayControl control = (TrayControl)_controls[i];
            if (control.Component == component)
            {
                int targetIndex = (forward ? i + 1 : i - 1);
                if (targetIndex >= 0 && targetIndex < _controls.Count)
                {
                    return ((TrayControl)_controls[targetIndex]).Component;
                }

                // Reached the end of the road.
                return null;
            }
        }

        // If we got here then the component isn't in our list. Prime the caller with either the first or the last.
        if (_controls.Count > 0)
        {
            int targetIndex = (forward ? 0 : _controls.Count - 1);
            return ((TrayControl)_controls[targetIndex]).Component;
        }

        return null;
    }

    /// <summary>
    ///  Accessor method for the location extender property. We offer this extender
    ///  to all non-visual components.
    /// </summary>
    [Category("Layout")]
    [Localizable(false)]
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [SRDescription("ControlLocationDescr")]
    [DesignOnly(true)]
    public Point GetLocation(IComponent receiver)
    {
        PropertyDescriptor loc = TypeDescriptor.GetProperties(receiver.GetType())["Location"];
        if (loc is not null)
        {
            // In this case the component already had a Location property,
            // and what the caller wants is the underlying components Location,
            // not the tray location. Why? Because we now use TrayLocation.
            return (Point)(loc.GetValue(receiver));
        }
        else
        {
            // If the component didn't already have a Location property,
            // then the caller really wants the tray location. Could be a 3rd party vendor.
            return GetTrayLocation(receiver);
        }
    }

    /// <summary>
    ///  Accessor method for the location extender property. We offer this extender
    ///  to all non-visual components.
    /// </summary>
    [Category("Layout")]
    [Localizable(false)]
    [Browsable(false)]
    [SRDescription("ControlLocationDescr")]
    [DesignOnly(true)]
    public Point GetTrayLocation(IComponent receiver)
    {
        Control c = TrayControl.FromComponent(receiver);
        if (c is null)
        {
            Debug.Fail("Anything we're extending should have a component view.");
            return default;
        }

        Point loc = c.Location;
        Point autoScrollLoc = AutoScrollPosition;
        return new Point(loc.X - autoScrollLoc.X, loc.Y - autoScrollLoc.Y);
    }

    /// <summary>
    ///  Gets the requested service type.
    /// </summary>
    protected override object GetService(Type serviceType)
    {
        object service = null;
        Debug.Assert(_serviceProvider is not null, "Trying to access services too late or too early.");
        if (_serviceProvider is not null)
        {
            service = _serviceProvider.GetService(serviceType);
        }

        return service;
    }

    /// <summary>
    ///  Returns true if the given component is being shown on the tray.
    /// </summary>
    public bool IsTrayComponent(IComponent comp)
    {
        if (TrayControl.FromComponent(comp) is null)
        {
            return false;
        }

        foreach (Control control in Controls)
        {
            if (control is TrayControl tc && tc.Component == comp)
            {
                return true;
            }
        }

        return false;
    }

    protected override void OnMouseDoubleClick(MouseEventArgs e)
    {
        // give our glyphs first chance at this
        if (_glyphManager is not null && _glyphManager.OnMouseDoubleClick(e))
        {
            // handled by a glyph - so don't send to the comp tray
            return;
        }

        base.OnDoubleClick(e);
        if (!TabOrderActive)
        {
            OnLostCapture();
            IEventBindingService eps = (IEventBindingService)GetService(typeof(IEventBindingService));
            eps?.ShowCode();
        }
    }

    /// <summary>
    ///  Inheriting classes should override this method to handle this event.
    ///  Call base.onGiveFeedback to send this event to any registered event listeners.
    /// </summary>
    protected override void OnGiveFeedback(GiveFeedbackEventArgs gfevent)
    {
        base.OnGiveFeedback(gfevent);
        GetOleDragHandler().DoOleGiveFeedback(gfevent);
    }

    /// <summary>
    ///  Called in response to a drag drop for OLE drag and drop. Here we
    ///  drop a toolbox component on our parent control.
    /// </summary>
    protected override void OnDragDrop(DragEventArgs de)
    {
        // This will be used once during PositionComponent to place the component at the drop point. It is automatically
        // set to null afterwards, so further components appear after the first one dropped.
        _mouseDropLocation = PointToClient(new Point(de.X, de.Y));
        _autoScrollPosBeforeDragging = AutoScrollPosition;

        if (_mouseDragTool is not null)
        {
            ToolboxItem tool = _mouseDragTool;
            _mouseDragTool = null;
            try
            {
                IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
                IDesigner designer = host.GetDesigner(host.RootComponent);
                if (designer is IToolboxUser itu)
                {
                    itu.ToolPicked(tool);
                }
                else
                {
                    CreateComponentFromTool(tool);
                }
            }
            catch (Exception e)
            {
                DisplayError(e);
                if (e.IsCriticalException())
                {
                    throw;
                }
            }

            de.Effect = DragDropEffects.Copy;
        }
        else
        {
            GetOleDragHandler().DoOleDragDrop(de);
        }

        _mouseDropLocation = InvalidPoint;
        ResumeLayout();
    }

    /// <summary>
    ///  Called in response to a drag enter for OLE drag and drop.
    /// </summary>
    protected override void OnDragEnter(DragEventArgs de)
    {
        if (!TabOrderActive)
        {
            SuspendLayout();
            _toolboxService ??= (IToolboxService)GetService(typeof(IToolboxService));

            OleDragDropHandler dragDropHandler = GetOleDragHandler();
            object[] dragComps = OleDragDropHandler.GetDraggingObjects(de);
            // Only assume the items came from the ToolBox if dragComps is null
            if (_toolboxService is not null && dragComps is null)
            {
                _mouseDragTool = _toolboxService.DeserializeToolboxItem(de.Data, (IDesignerHost)GetService(typeof(IDesignerHost)));
            }

            if (_mouseDragTool is not null)
            {
                Debug.Assert((de.AllowedEffect & (DragDropEffects.Move | DragDropEffects.Copy)) != 0, "DragDropEffect.Move | .Copy isn't allowed?");
                if ((de.AllowedEffect & DragDropEffects.Move) != 0)
                {
                    de.Effect = DragDropEffects.Move;
                }
                else
                {
                    de.Effect = DragDropEffects.Copy;
                }
            }
            else
            {
                dragDropHandler.DoOleDragEnter(de);
            }
        }
    }

    /// <summary>
    ///  Called when a drag-drop operation leaves the control designer view
    /// </summary>
    protected override void OnDragLeave(EventArgs e)
    {
        _mouseDragTool = null;
        GetOleDragHandler().DoOleDragLeave();
        ResumeLayout();
    }

    /// <summary>
    ///  Called when a drag drop object is dragged over the control designer view
    /// </summary>
    protected override void OnDragOver(DragEventArgs de)
    {
        if (_mouseDragTool is not null)
        {
            Debug.Assert((de.AllowedEffect & DragDropEffects.Copy) != 0, "DragDropEffect.Move isn't allowed?");
            de.Effect = DragDropEffects.Copy;
        }
        else
        {
            GetOleDragHandler().DoOleDragOver(de);
        }
    }

    /// <summary>
    ///  Forces the layout of any docked or anchored child controls.
    /// </summary>
    protected override void OnLayout(LayoutEventArgs levent)
    {
        DoAutoArrange(false);
        // make sure selection service redraws
        Invalidate(true);
        base.OnLayout(levent);
    }

    /// <summary>
    ///  This is called when we lose capture. Here we get rid of any
    ///  rubber band we were drawing. You should put any cleanup
    ///  code in here.
    /// </summary>
    protected virtual void OnLostCapture()
    {
        if (_mouseDragStart != InvalidPoint)
        {
            Cursor.Clip = Rectangle.Empty;
            if (_mouseDragEnd != InvalidPoint)
            {
                DrawRubber(_mouseDragStart, _mouseDragEnd);
                _mouseDragEnd = InvalidPoint;
            }

            _mouseDragStart = InvalidPoint;
        }
    }

    private void DrawRubber(Point start, Point end)
    {
        _mouseDragWorkspace.X = Math.Min(start.X, end.X);
        _mouseDragWorkspace.Y = Math.Min(start.Y, end.Y);
        _mouseDragWorkspace.Width = Math.Abs(end.X - start.X);
        _mouseDragWorkspace.Height = Math.Abs(end.Y - start.Y);
        _mouseDragWorkspace = RectangleToScreen(_mouseDragWorkspace);
        ControlPaint.DrawReversibleFrame(_mouseDragWorkspace, BackColor, FrameStyle.Dashed);
    }

    /// <summary>
    ///  Inheriting classes should override this method to handle this event.
    ///  Call base.onMouseDown to send this event to any registered event listeners.
    /// </summary>
    protected override void OnMouseDown(MouseEventArgs e)
    {
        // give our glyphs first chance at this
        if (_glyphManager is not null && _glyphManager.OnMouseDown(e))
        {
            // handled by a glyph - so don't send to the comp tray
            return;
        }

        base.OnMouseDown(e);
        if (!TabOrderActive)
        {
            _toolboxService ??= (IToolboxService)GetService(typeof(IToolboxService));

            FocusDesigner();
            if (e.Button == MouseButtons.Left && _toolboxService is not null)
            {
                ToolboxItem tool = _toolboxService.GetSelectedToolboxItem((IDesignerHost)GetService(typeof(IDesignerHost)));
                if (tool is not null)
                {
                    // mouseDropLocation is checked in PositionControl,
                    // which should get called as a result of adding a new component.
                    // This allows us to set the position without flickering,
                    // while still providing support for auto layout if the control was double clicked or
                    // added through extensibility.
                    _mouseDropLocation = new Point(e.X, e.Y);
                    try
                    {
                        CreateComponentFromTool(tool);
                        _toolboxService.SelectedToolboxItemUsed();
                    }
                    catch (Exception ex)
                    {
                        DisplayError(ex);
                        if (ex.IsCriticalException())
                        {
                            throw;
                        }
                    }

                    _mouseDropLocation = InvalidPoint;
                    return;
                }
            }

            // If it is the left button, start a rubber band drag to lasso controls.
            if (e.Button == MouseButtons.Left)
            {
                _mouseDragStart = new Point(e.X, e.Y);
                Capture = true;
                Cursor.Clip = RectangleToScreen(ClientRectangle);
            }
            else
            {
                try
                {
                    ISelectionService ss = (ISelectionService)GetService(typeof(ISelectionService));
                    ss?.SetSelectedComponents(new object[] { _mainDesigner.Component });
                }
                catch (Exception ex) when (!ex.IsCriticalException())
                {
                    // Nothing we can really do here; just eat it.
                }
            }
        }
    }

    /// <summary>
    ///  Inheriting classes should override this method to handle this event.
    ///  Call base.onMouseMove to send this event to any registered event listeners.
    /// </summary>
    protected override void OnMouseMove(MouseEventArgs e)
    {
        // give our glyphs first chance at this
        if (_glyphManager is not null && _glyphManager.OnMouseMove(e))
        {
            // handled by a glyph - so don't send to the comp tray
            return;
        }

        base.OnMouseMove(e);

        // If we are dragging, then draw our little rubber band.
        if (_mouseDragStart != InvalidPoint)
        {
            if (_mouseDragEnd != InvalidPoint)
            {
                DrawRubber(_mouseDragStart, _mouseDragEnd);
            }
            else
            {
                _mouseDragEnd = new Point(0, 0);
            }

            _mouseDragEnd.X = e.X;
            _mouseDragEnd.Y = e.Y;
            DrawRubber(_mouseDragStart, _mouseDragEnd);
        }
    }

    /// <summary>
    ///  Inheriting classes should override this method to handle this event.
    ///  Call base.onMouseUp to send this event to any registered event listeners.
    /// </summary>
    protected override void OnMouseUp(MouseEventArgs e)
    {
        // give our glyphs first chance at this
        if (_glyphManager is not null && _glyphManager.OnMouseUp(e))
        {
            // handled by a glyph - so don't send to the comp tray
            return;
        }

        if (_mouseDragStart != InvalidPoint && e.Button == MouseButtons.Left)
        {
            IComponent[] comps;
            Capture = false;
            Cursor.Clip = Rectangle.Empty;
            if (_mouseDragEnd != InvalidPoint)
            {
                DrawRubber(_mouseDragStart, _mouseDragEnd);
                Rectangle rect = new Rectangle
                {
                    X = Math.Min(_mouseDragStart.X, e.X),
                    Y = Math.Min(_mouseDragStart.Y, e.Y),
                    Width = Math.Abs(e.X - _mouseDragStart.X),
                    Height = Math.Abs(e.Y - _mouseDragStart.Y)
                };
                comps = GetComponentsInRect(rect);
                _mouseDragEnd = InvalidPoint;
            }
            else
            {
                comps = [];
            }

            if (comps.Length == 0)
            {
                comps = [_mainDesigner.Component];
            }

            try
            {
                ISelectionService ss = (ISelectionService)GetService(typeof(ISelectionService));
                ss?.SetSelectedComponents(comps);
            }
            catch (Exception ex) when (!ex.IsCriticalException())
            {
                // Nothing we can really do here; just eat it.
            }

            _mouseDragStart = InvalidPoint;
        }

        base.OnMouseUp(e);
    }

    private IComponent[] GetComponentsInRect(Rectangle rect)
    {
        List<IComponent> list = [];
        int controlCount = Controls.Count;
        for (int i = 0; i < controlCount; i++)
        {
            Control child = Controls[i];
            Rectangle bounds = child.Bounds;
            if (child is TrayControl tc && bounds.IntersectsWith(rect))
            {
                list.Add(tc.Component);
            }
        }

        return [.. list];
    }

    protected override void OnPaint(PaintEventArgs pe)
    {
        if (_fResetAmbient || _fSelectionChanged)
        {
            _fResetAmbient = false;
            _fSelectionChanged = false;
            IUIService uiService = (IUIService)GetService(typeof(IUIService));
            if (uiService is not null)
            {
                Color styleColor;
                if (uiService.Styles["ArtboardBackground"] is Color backgroundColor)
                {
                    styleColor = backgroundColor;
                }

                // Can't use 'as' here since Color is a value type
                else if (uiService.Styles["VsColorDesignerTray"] is Color trayColor)
                {
                    styleColor = trayColor;
                }
                else if (uiService.Styles["HighlightColor"] is Color highlightColor)
                {
                    // Since v1, we have had code here that checks for HighlightColor,
                    // so some hosts (like WinRes) have been setting it.
                    // If VsColorDesignerTray isn't present, we look for HighlightColor for backward compatibility.
                    styleColor = highlightColor;
                }
                else
                {
                    // No style color provided? Let's pick a default.
                    styleColor = SystemColors.Info;
                }

                BackColor = styleColor;
                Font = (Font)uiService.Styles["DialogFont"];
                foreach (Control ctl in _controls)
                {
                    ctl.BackColor = styleColor;
                    ctl.ForeColor = ForeColor;
                }
            }
        }

        base.OnPaint(pe);
        Graphics gr = pe.Graphics;
        // Now, if we have a selection, paint it
        if (_selectedObjects is not null)
        {
            bool first = true; // indicates the first iteration of our foreach loop
            HatchBrush selectionBorderBrush;
            if (SystemInformation.HighContrast)
            {
                selectionBorderBrush = new HatchBrush(HatchStyle.Percent50, SystemColors.HighlightText, Color.Transparent);
            }
            else
            {
                selectionBorderBrush = new HatchBrush(HatchStyle.Percent50, SystemColors.ControlDarkDark, Color.Transparent);
            }

            try
            {
                foreach (object o in _selectedObjects)
                {
                    Control c = ((IOleDragClient)this).GetControlForComponent(o);
                    if (c is not null && c.Visible)
                    {
                        Rectangle innerRect = c.Bounds;
                        if (SystemInformation.HighContrast)
                        {
                            c.ForeColor = SystemColors.HighlightText;
                            c.BackColor = SystemColors.Highlight;
                        }

                        NoResizeHandleGlyph glyph = new(innerRect, SelectionRules.None, first, null);
                        gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Top));
                        gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Bottom));
                        gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Left));
                        gr.FillRectangle(selectionBorderBrush, DesignerUtils.GetBoundsForNoResizeSelectionType(innerRect, SelectionBorderGlyphType.Right));
                        // Need to draw this one last
                        DesignerUtils.DrawNoResizeHandle(gr, glyph.Bounds, first);
                    }

                    first = false;
                }
            }
            finally
            {
                selectionBorderBrush?.Dispose();
            }
        }

        // paint any glyphs
        _glyphManager?.OnPaintGlyphs(pe);
    }

    /// <summary>
    ///  Sets the cursor. You may override this to set your own
    ///  cursor.
    /// </summary>
    protected virtual void OnSetCursor()
    {
        _toolboxService ??= (IToolboxService)GetService(typeof(IToolboxService));

        if (_toolboxService is null || !_toolboxService.SetCursor())
        {
            Cursor.Current = Cursors.Default;
        }
    }

    /// <summary>
    ///  Removes a component from the tray.
    /// </summary>
    public virtual void RemoveComponent(IComponent component)
    {
        TrayControl c = TrayControl.FromComponent(component);
        if (c is not null)
        {
            try
            {
                InheritanceAttribute attr = c.InheritanceAttribute;
                if (attr.InheritanceLevel != InheritanceLevel.NotInherited && _inheritanceUI is not null)
                {
                    _inheritanceUI.RemoveInheritedControl(c);
                }

                _controls?.Remove(c);
            }
            finally
            {
                c.Dispose();
            }
        }
    }

    /// <summary>
    ///  Accessor method for the location extender property. We offer this extender
    ///  to all non-visual components.
    /// </summary>
    public void SetLocation(IComponent receiver, Point location)
    {
        // This really should only be called when we are loading.
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        if (host is not null && host.Loading)
        {
            // If we are loading, and we get called here,
            // that's because we have provided the extended Location property.
            // In this case we are loading an old project,
            // and what we are really setting is the tray location.
            SetTrayLocation(receiver, location);
        }
        else
        {
            // we are not loading
            PropertyDescriptor loc = TypeDescriptor.GetProperties(receiver.GetType())["Location"];
            if (loc is not null)
            {
                // so if the component already had the Location property,
                // what the caller wants is really the underlying component's Location property.
                loc.SetValue(receiver, location);
            }
            else
            {
                // if the component didn't have a Location property, then the caller really wanted the tray location.
                SetTrayLocation(receiver, location);
            }
        }
    }

    /// <summary>
    ///  Accessor method for the location extender property. We offer this extender
    ///  to all non-visual components.
    /// </summary>
    public void SetTrayLocation(IComponent receiver, Point location)
    {
        TrayControl c = TrayControl.FromComponent(receiver);
        if (c is null)
        {
            Debug.Fail("Anything we're extending should have a component view.");
            return;
        }

        if (c.Parent == this)
        {
            Point autoScrollLoc = AutoScrollPosition;
            location = new Point(location.X + autoScrollLoc.X, location.Y + autoScrollLoc.Y);
            if (c.Visible)
            {
                RearrangeInAutoSlots(c, location);
            }
        }
        else if (!c.Location.Equals(location))
        {
            c.Location = location;
            c.Positioned = true;
        }
    }

    /// <summary>
    ///  We override our base class's WndProc to monitor certain messages.
    /// </summary>
    protected override void WndProc(ref Message m)
    {
        switch (m.MsgInternal)
        {
            case PInvokeCore.WM_CANCELMODE:
                // When we get cancelmode (i.e. you tabbed away to another window) then we want to cancel
                // any pending drag operation.
                OnLostCapture();
                break;
            case PInvokeCore.WM_SETCURSOR:
                OnSetCursor();
                return;
            case PInvokeCore.WM_HSCROLL:
            case PInvokeCore.WM_VSCROLL:
                // When we scroll, we reposition a control without causing a property change event.
                // Therefore, we must tell the selection UI service to sync itself.
                base.WndProc(ref m);
                _selectionUISvc?.SyncSelection();

                return;
            case PInvokeCore.WM_STYLECHANGED:
                // When the scroll bars first appear, we need to invalidate so we properly paint our grid.
                Invalidate();
                break;
            case PInvokeCore.WM_CONTEXTMENU:
                {
                    // Pop a context menu for the composition designer.
                    Point location = PARAM.ToPoint(m.LParamInternal);
                    if (location.X == -1 && location.Y == -1)
                    {
                        // For shift-F10.
                        location = MousePosition;
                    }

                    OnContextMenu(location);
                    break;
                }

            case PInvokeCore.WM_NCHITTEST:
                {
                    if (_glyphManager is not null)
                    {
                        // Get a hit test on any glyphs that we are managing this way.
                        // We know where to route appropriate messages.
                        Point location = PARAM.ToPoint(m.LParamInternal);
                        location.Offset(PointToClient(default));
                        _glyphManager.GetHitTest(location);
                    }

                    base.WndProc(ref m);
                    break;
                }

            default:
                base.WndProc(ref m);
                break;
        }
    }

    internal static TrayControl GetTrayControlFromComponent(IComponent comp)
    {
        return TrayControl.FromComponent(comp);
    }

    private bool TabOrderActive
    {
        get
        {
            if (!_queriedTabOrder)
            {
                _queriedTabOrder = true;
                IMenuCommandService mcs = MenuService;
                if (mcs is not null)
                {
                    _tabOrderCommand = mcs.FindCommand(StandardCommands.TabOrder);
                }
            }

            return _tabOrderCommand is not null && _tabOrderCommand.Checked;
        }
    }

    private InheritanceUI InheritanceUI
    {
        get
        {
            _inheritanceUI ??= new InheritanceUI();

            return _inheritanceUI;
        }
    }

    private IMenuCommandService MenuService
    {
        get
        {
            _menuCommandService ??= (IMenuCommandService)GetService(typeof(IMenuCommandService));

            return _menuCommandService;
        }
    }

    internal void FocusDesigner()
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
        if (host is not null && host.RootComponent is not null)
        {
            if (host.GetDesigner(host.RootComponent) is IRootDesigner rd)
            {
                ViewTechnology[] techs = rd.SupportedTechnologies;
                if (techs.Length > 0)
                {
                    if (rd.GetView(techs[0]) is Control view)
                    {
                        view.Focus();
                    }
                }
            }
        }
    }

    internal Size ParentGridSize
    {
        get
        {
            if (_mainDesigner is ParentControlDesigner designer)
            {
                return designer.ParentGridSize;
            }

            return new Size(8, 8);
        }
    }

    internal void UpdatePastePositions(List<Control> components)
    {
        foreach (TrayControl c in components)
        {
            if (!CanDisplayComponent(c.Component))
            {
                return;
            }

            if (_mouseDropLocation == InvalidPoint)
            {
                Control prevCtl = null;
                if (_controls.Count > 1)
                {
                    prevCtl = _controls[^1];
                }

                PositionInNextAutoSlot(c, prevCtl, true);
            }
            else
            {
                PositionControl(c);
            }

            c.BringToFront();
        }
    }

    private void PositionControl(TrayControl c)
    {
        Debug.Assert(c.Visible, $"TrayControl for {c.Component} should not be positioned");
        if (!_autoArrange)
        {
            if (_mouseDropLocation != InvalidPoint)
            {
                if (!c.Location.Equals(_mouseDropLocation))
                {
                    c.Location = _mouseDropLocation;
                }
            }
            else
            {
                Control prevCtl = null;
                if (_controls.Count > 1)
                {
                    // PositionControl can be called when all the controls have been added
                    // (from IOleDragClient.AddComponent), so we can't use the old way of
                    // looking up the previous control (prevCtl = controls[controls.Count - 2]
                    int index = _controls.IndexOf(c);
                    Debug.Assert(index >= 1, "Got the wrong index, how could that be?");
                    if (index >= 1)
                    {
                        prevCtl = _controls[index - 1];
                    }
                }

                PositionInNextAutoSlot(c, prevCtl, true);
            }
        }
        else
        {
            if (_mouseDropLocation != InvalidPoint)
            {
                RearrangeInAutoSlots(c, _mouseDropLocation);
            }
            else
            {
                Control prevCtl = null;
                if (_controls.Count > 1)
                {
                    int index = _controls.IndexOf(c);
                    Debug.Assert(index >= 1, "Got the wrong index, how could that be?");
                    if (index >= 1)
                    {
                        prevCtl = _controls[index - 1];
                    }
                }

                PositionInNextAutoSlot(c, prevCtl, true);
            }
        }
    }

    internal void RearrangeInAutoSlots(Control c, Point pos)
    {
        Debug.Assert(_controls.IndexOf(c) != -1, "Add control to the list of controls before autoarranging.!!!");
        Debug.Assert(Visible == c.Visible, $"TrayControl for {((TrayControl)c).Component} should not be positioned");

        TrayControl tc = (TrayControl)c;
        tc.Positioned = true;
        tc.Location = pos;
    }

    private bool PositionInNextAutoSlot(TrayControl c, Control prevCtl, bool dirtyDesigner)
    {
        Debug.Assert(c.Visible, $"TrayControl for {c.Component} should not be positioned");
        if (_whiteSpace.IsEmpty)
        {
            Debug.Assert(_selectionUISvc is not null, "No SelectionUIService available for tray.");
            _whiteSpace = new Point(_selectionUISvc.GetAdornmentDimensions(AdornmentType.GrabHandle));
            _whiteSpace.X = _whiteSpace.X * 2 + 3;
            _whiteSpace.Y = _whiteSpace.Y * 2 + 3;
        }

        if (prevCtl is null)
        {
            Rectangle display = DisplayRectangle;
            Point newLoc = new(display.X + _whiteSpace.X, display.Y + _whiteSpace.Y);
            if (!c.Location.Equals(newLoc))
            {
                c.Location = newLoc;
                if (dirtyDesigner)
                {
                    IComponent comp = c.Component;
                    Debug.Assert(comp is not null, "Component for the TrayControl is null");
                    PropertyDescriptor ctlLocation = TypeDescriptor.GetProperties(comp)["TrayLocation"];
                    if (ctlLocation is not null)
                    {
                        Point autoScrollLoc = AutoScrollPosition;
                        newLoc = new Point(newLoc.X - autoScrollLoc.X, newLoc.Y - autoScrollLoc.Y);
                        ctlLocation.SetValue(comp, newLoc);
                    }
                }
                else
                {
                    c.Location = newLoc;
                }

                return true;
            }
        }
        else
        {
            // Calculate the next location for this control.
            Rectangle bounds = prevCtl.Bounds;
            Point newLoc = new(bounds.X + bounds.Width + _whiteSpace.X, bounds.Y);

            // Check to see if it goes over the edge of our window. If it does, then wrap it.
            if (newLoc.X + c.Size.Width > Size.Width)
            {
                newLoc.X = _whiteSpace.X;
                newLoc.Y += bounds.Height + _whiteSpace.Y;
            }

            if (!c.Location.Equals(newLoc))
            {
                if (dirtyDesigner)
                {
                    IComponent comp = c.Component;
                    Debug.Assert(comp is not null, "Component for the TrayControl is null");
                    PropertyDescriptor ctlLocation = TypeDescriptor.GetProperties(comp)["TrayLocation"];
                    if (ctlLocation is not null)
                    {
                        Point autoScrollLoc = AutoScrollPosition;
                        newLoc = new Point(newLoc.X - autoScrollLoc.X, newLoc.Y - autoScrollLoc.Y);
                        ctlLocation.SetValue(comp, newLoc);
                    }
                }
                else
                {
                    c.Location = newLoc;
                }

                return true;
            }
        }

        return false;
    }

    internal class TrayControl : Control
    {
        // Values that define this tray control
        private readonly IComponent _component; // the component this control is representing
        private Image _toolboxBitmap; // the bitmap used to represent the component
        private int _cxIcon; // the dimensions of the bitmap
        private int _cyIcon; // the dimensions of the bitmap
        private readonly InheritanceAttribute _inheritanceAttribute;

        // Services that we use often enough to cache.
        private readonly ComponentTray _tray;
        // transient values that are used during mouse drags
        private Point _mouseDragLast = InvalidPoint; // the last position of the mouse during a drag.
        private bool _mouseDragMoved; // has the mouse been moved during this drag?
        private bool _ctrlSelect; // was the ctrl key down on the mouse down?
        private bool _positioned; // Have we given this control an explicit location yet?
        private const int WhiteSpace = 5;
        private readonly int _borderWidth;
        internal bool _fRecompute; // This flag tells the TrayControl that it needs to retrieve the font and the background color before painting.

        /// <summary>
        ///  Creates a new TrayControl based on the component.
        /// </summary>
        public TrayControl(ComponentTray tray, IComponent component)
        {
            _tray = tray;
            _component = component;
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.Selectable, false);
            _borderWidth = SystemInformation.BorderSize.Width;
            UpdateIconInfo();

            IComponentChangeService cs = (IComponentChangeService)tray.GetService(typeof(IComponentChangeService));
            if (cs is not null)
            {
                cs.ComponentRename += OnComponentRename;
            }

            ISite site = component.Site;
            string name = null;

            if (site is not null)
            {
                name = site.Name;
                IDictionaryService ds = (IDictionaryService)site.GetService(typeof(IDictionaryService));
                Debug.Assert(ds is not null, "ComponentTray relies on IDictionaryService, which is not available.");
                ds?.SetValue(GetType(), this);
            }

            // We always want name to have something in it, so we default to the class name.
            // This way the design instance contains something semi-intuitive if we don't have a site.
            name ??= component.GetType().Name;

            Text = name;
            _inheritanceAttribute = (InheritanceAttribute)TypeDescriptor.GetAttributes(component)[typeof(InheritanceAttribute)];
            TabStop = false;
        }

        /// <summary>
        ///  Retrieves the component this control is representing.
        /// </summary>
        public IComponent Component
        {
            get => _component;
        }

        public override Font Font
        {
            get => _tray.Font;
        }

        public InheritanceAttribute InheritanceAttribute
        {
            get => _inheritanceAttribute;
        }

        public bool Positioned
        {
            get => _positioned;
            set => _positioned = value;
        }

        /// <summary>
        ///  Adjusts the size of the control based on the contents.
        /// </summary>
        // CONSIDER: this method gets called three or four times per component,
        // and is even reentrant (CreateGraphics can force handle creation, and OnCreateHandle calls this method).
        // There's probably a better way to do this, but since this doesn't seem to be on the critical path,
        // I'm not going to lose sleep over it.
        private void AdjustSize()
        {
            // CONSIDER: this forces handle creation. Can we delay this calculation?
            Graphics gr = CreateGraphics();
            try
            {
                Size sz = Size.Ceiling(gr.MeasureString(Text, Font));

                Rectangle rc = Bounds;

                if (_tray.ShowLargeIcons)
                {
                    rc.Width = Math.Max(_cxIcon, sz.Width) + 4 * _borderWidth + 2 * WhiteSpace;
                    rc.Height = _cyIcon + 2 * WhiteSpace + sz.Height + 4 * _borderWidth;
                }
                else
                {
                    rc.Width = _cxIcon + sz.Width + 4 * _borderWidth + 2 * WhiteSpace;
                    rc.Height = Math.Max(_cyIcon, sz.Height) + 4 * _borderWidth;
                }

                Bounds = rc;
                Invalidate();
            }

            finally
            {
                gr?.Dispose();
            }

            _tray._glyphManager?.UpdateLocation(this);
        }

        protected override AccessibleObject CreateAccessibilityInstance() => new TrayControlAccessibleObject(this, _tray);

        /// <summary>
        ///  Destroys this control. Views automatically destroy themselves when they are removed from the design container.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ISite site = _component.Site;
                if (site is not null)
                {
                    if (site.TryGetService(out IComponentChangeService cs))
                    {
                        cs.ComponentRename -= OnComponentRename;
                    }

                    if (site.TryGetService(out IDictionaryService ds))
                    {
                        ds.SetValue(typeof(TrayControl), null);
                    }
                }
            }

            base.Dispose(disposing);
        }

        /// <summary>
        ///  Retrieves the tray control object for the given component.
        /// </summary>
        public static TrayControl FromComponent(IComponent component)
        {
            if (component is null)
            {
                return null;
            }

            if (component.Site.TryGetService(out IDictionaryService ds))
            {
                return (TrayControl)ds.GetValue(typeof(TrayControl));
            }

            return null;
        }

        /// <summary>
        ///  Delegate that is called in response to a name change.
        ///  Here we update our own stashed version of the name, recalculate our size and repaint.
        /// </summary>
        private void OnComponentRename(object sender, ComponentRenameEventArgs e)
        {
            if (e.Component == _component)
            {
                Text = e.NewName;
                AdjustSize();
            }
        }

        /// <summary>
        ///  Overrides handle creation notification for a control. Here we just ensure that we're the proper size.
        /// </summary>
        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            AdjustSize();
        }

        /// <summary>
        ///  Called in response to a double-click of the left mouse button. The default behavior here calls onDoubleClick on IMouseHandler
        /// </summary>
        protected override void OnDoubleClick(EventArgs e)
        {
            base.OnDoubleClick(e);
            if (!_tray.TabOrderActive)
            {
                IDesignerHost host = (IDesignerHost)_tray.GetService(typeof(IDesignerHost));
                Debug.Assert(host is not null, "Component tray does not have access to designer host.");
                if (host is not null)
                {
                    _mouseDragLast = InvalidPoint;
                    Capture = false;
                    // We try to get a designer for the component and let it view the event. If this fails, then we'll try to do it ourselves.
                    IDesigner designer = host.GetDesigner(_component);
                    if (designer is null)
                    {
                        ViewDefaultEvent(_component);
                    }
                    else
                    {
                        designer.DoDefaultAction();
                    }
                }
            }
        }

        /// <summary>
        ///  Terminates our drag operation.
        /// </summary>
        private void OnEndDrag(bool cancel)
        {
            _mouseDragLast = InvalidPoint;
            if (!_mouseDragMoved)
            {
                if (_ctrlSelect)
                {
                    ISelectionService sel = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                    sel?.SetSelectedComponents(new object[] { Component }, SelectionTypes.Primary);

                    _ctrlSelect = false;
                }

                return;
            }

            _mouseDragMoved = false;
            _ctrlSelect = false;
            Capture = false;
            OnSetCursor();

            // And now finish the drag.
            Debug.Assert(_tray._selectionUISvc is not null, "We shouldn't be able to begin a drag without this");
            if (_tray._selectionUISvc is not null && _tray._selectionUISvc.Dragging)
            {
                _tray._selectionUISvc.EndDrag(cancel);
            }
        }

        /// <summary>
        ///  Called when the mouse button is pressed down. Here, we provide drag support for the component.
        /// </summary>
        protected override void OnMouseDown(MouseEventArgs me)
        {
            base.OnMouseDown(me);
            if (_tray.TabOrderActive)
            {
                return;
            }

            _tray.FocusDesigner();

            // If this is the left mouse button, then begin a drag.
            if (me.Button == MouseButtons.Left)
            {
                Capture = true;
                _mouseDragLast = PointToScreen(new Point(me.X, me.Y));

                // If the CTRL key isn't down, select this component, otherwise, we wait until the mouse up.
                // Make sure the component is selected.
                _ctrlSelect = PInvoke.GetKeyState((int)Keys.ControlKey) != 0;
                if (!_ctrlSelect)
                {
                    ISelectionService sel = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                    // Make sure the component is selected
                    sel?.SetSelectedComponents(new object[] { Component }, SelectionTypes.Primary);
                }
            }
        }

        /// <summary>
        ///  Called when the mouse is moved over the component.
        ///  We update our drag information here if we're dragging the component around.
        /// </summary>
        protected override void OnMouseMove(MouseEventArgs me)
        {
            base.OnMouseMove(me);
            if (_mouseDragLast == InvalidPoint)
            {
                return;
            }

            if (!_mouseDragMoved)
            {
                Size minDrag = SystemInformation.DragSize;
                Size minDblClick = SystemInformation.DoubleClickSize;
                minDrag.Width = Math.Max(minDrag.Width, minDblClick.Width);
                minDrag.Height = Math.Max(minDrag.Height, minDblClick.Height);
                // we have to make sure the mouse moved farther than the minimum drag distance before we actually start the drag
                Point newPt = PointToScreen(new Point(me.X, me.Y));
                if (_mouseDragLast == InvalidPoint ||
                    (Math.Abs(_mouseDragLast.X - newPt.X) < minDrag.Width &&
                     Math.Abs(_mouseDragLast.Y - newPt.Y) < minDrag.Height))
                {
                    return;
                }
                else
                {
                    _mouseDragMoved = true;
                    // we're on the move, so we're not in a ctrlSelect
                    _ctrlSelect = false;
                }
            }

            try
            {
                // Make sure the component is selected
                ISelectionService sel = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                sel?.SetSelectedComponents(new object[] { Component }, SelectionTypes.Primary);

                // Notify the selection service that all the components are in the "mouse down" mode.
                if (_tray._selectionUISvc is not null && _tray._selectionUISvc.BeginDrag(SelectionRules.Visible | SelectionRules.Moveable, _mouseDragLast.X, _mouseDragLast.Y))
                {
                    OnSetCursor();
                }
            }
            finally
            {
                _mouseDragMoved = false;
                _mouseDragLast = InvalidPoint;
            }
        }

        /// <summary>
        ///  Called when the mouse button is released. Here, we finish our drag if one was started.
        /// </summary>
        protected override void OnMouseUp(MouseEventArgs me)
        {
            base.OnMouseUp(me);
            OnEndDrag(false);
        }

        /// <summary>
        ///  Called when we are to display our context menu for this component.
        /// </summary>
        private void OnContextMenu(Point location)
        {
            if (!_tray.TabOrderActive)
            {
                Capture = false;
                // Ensure that this component is selected.
                ISelectionService s = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                if (s is not null && !s.GetComponentSelected(_component))
                {
                    s.SetSelectedComponents(new object[] { _component }, SelectionTypes.Replace);
                }

                IMenuCommandService mcs = _tray.MenuService;
                if (mcs is not null)
                {
                    Capture = false;
                    Cursor.Clip = Rectangle.Empty;
                    mcs.ShowContextMenu(MenuCommands.TraySelectionMenu, location.X, location.Y);
                }
            }
        }

        /// <summary>
        ///  Painting for our control.
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            if (_fRecompute)
            {
                _fRecompute = false;
                UpdateIconInfo();
            }

            base.OnPaint(e);
            Rectangle rc = ClientRectangle;
            rc.X += WhiteSpace + _borderWidth;
            rc.Y += _borderWidth;
            rc.Width -= (2 * _borderWidth + WhiteSpace);
            rc.Height -= 2 * _borderWidth;
            StringFormat format = new();
            Brush foreBrush = new SolidBrush(ForeColor);
            try
            {
                format.Alignment = StringAlignment.Center;
                if (_tray.ShowLargeIcons)
                {
                    if (_toolboxBitmap is not null)
                    {
                        int x = rc.X + (rc.Width - _cxIcon) / 2;
                        int y = rc.Y + WhiteSpace;
                        e.Graphics.DrawImage(_toolboxBitmap, new Rectangle(x, y, _cxIcon, _cyIcon));
                    }

                    rc.Y += (_cyIcon + WhiteSpace);
                    rc.Height -= _cyIcon;
                    e.Graphics.DrawString(Text, Font, foreBrush, rc, format);
                }
                else
                {
                    if (_toolboxBitmap is not null)
                    {
                        int y = rc.Y + (rc.Height - _cyIcon) / 2;
                        e.Graphics.DrawImage(_toolboxBitmap, new Rectangle(rc.X, y, _cxIcon, _cyIcon));
                    }

                    rc.X += (_cxIcon + _borderWidth);
                    rc.Width -= _cxIcon;
                    rc.Y += 3;
                    e.Graphics.DrawString(Text, Font, foreBrush, rc);
                }
            }

            finally
            {
                format?.Dispose();

                foreBrush?.Dispose();
            }

            // If this component is being inherited, paint it as such
            if (!InheritanceAttribute.NotInherited.Equals(_inheritanceAttribute))
            {
                InheritanceUI iui = _tray.InheritanceUI;
                if (iui is not null)
                {
                    e.Graphics.DrawImage(InheritanceUI.InheritanceGlyph, 0, 0);
                }
            }
        }

        /// <summary>
        ///  Overrides control's FontChanged. Here we re-adjust our size if the font changes.
        /// </summary>
        protected override void OnFontChanged(EventArgs e)
        {
            AdjustSize();
            base.OnFontChanged(e);
        }

        /// <summary>
        ///  Overrides control's LocationChanged. Here, we make sure that any glyphs associated with us are also relocated.
        /// </summary>
        protected override void OnLocationChanged(EventArgs e)
        {
            _tray._glyphManager?.UpdateLocation(this);
        }

        /// <summary>
        ///  Overrides control's TextChanged. Here we re-adjust our size if the font changes.
        /// </summary>
        protected override void OnTextChanged(EventArgs e)
        {
            AdjustSize();
            base.OnTextChanged(e);
        }

        /// <summary>
        ///  Called each time the cursor needs to be set.
        ///  The ControlDesigner behavior here will set the cursor to one of three things:
        ///  1. If the selection UI service shows a locked selection, or if there is no location property
        ///     on the control, then the default arrow will be set.
        ///  2. Otherwise, the four headed arrow will be set to indicate that the component can be clicked and moved.
        ///  3. If the user is currently dragging a component, the crosshair cursor will be used instead
        ///     of the four headed arrow.
        /// </summary>
        private void OnSetCursor()
        {
            // Check that the component is not locked.
            PropertyDescriptor prop;
            try
            {
                prop = TypeDescriptor.GetProperties(_component)["Locked"];
            }
            catch (FileNotFoundException e)
            {
                // In case an unhandled exception was encountered,
                // we don't want to leave the cursor with some strange shape.
                // Currently we have watson logs with FileNotFoundException only,
                // so we are scoping the catch only to that type.
                Cursor.Current = Cursors.Default;
                Debug.Fail(e.Message);
                return;
            }

            if (prop is not null && ((bool)prop.GetValue(_component)))
            {
                Cursor.Current = Cursors.Default;
                return;
            }

            // Ask the tray to see if the tab order UI is not running.
            if (_tray.TabOrderActive)
            {
                Cursor.Current = Cursors.Default;
                return;
            }

            if (_mouseDragMoved)
            {
                Cursor.Current = Cursors.Default;
            }
            else if (_mouseDragLast != InvalidPoint)
            {
                Cursor.Current = Cursors.Cross;
            }
            else
            {
                Cursor.Current = Cursors.SizeAll;
            }
        }

        protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
        {
            if (!_tray.AutoArrange ||
                (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
                (specified & BoundsSpecified.Height) == BoundsSpecified.Height)
            {
                base.SetBoundsCore(x, y, width, height, specified);
            }

            Rectangle bounds = Bounds;
            Size parentGridSize = _tray.ParentGridSize;
            if (Math.Abs(bounds.X - x) > parentGridSize.Width || Math.Abs(bounds.Y - y) > parentGridSize.Height)
            {
                base.SetBoundsCore(x, y, width, height, specified);
            }
        }

        protected override void SetVisibleCore(bool value)
        {
            if (value && !_tray.CanDisplayComponent(_component))
            {
                return;
            }

            base.SetVisibleCore(value);
        }

        public override string ToString() => $"ComponentTray: {_component}";

        internal void UpdateIconInfo()
        {
            ToolboxBitmapAttribute attr = (ToolboxBitmapAttribute)TypeDescriptor.GetAttributes(_component)[typeof(ToolboxBitmapAttribute)];
            if (attr is not null)
            {
                _toolboxBitmap = attr.GetImage(_component, _tray.ShowLargeIcons);
            }

            // Get the size of the bitmap so we can size our component correctly.
            if (_toolboxBitmap is null)
            {
                _cxIcon = 0;
                _cyIcon = SystemInformation.IconSize.Height;
            }
            else
            {
                Size sz = _toolboxBitmap.Size;
                _cxIcon = sz.Width;
                _cyIcon = sz.Height;
            }

            AdjustSize();
        }

        /// <summary>
        ///  This creates a method signature in the source code file for the default event
        ///  on the component and navigates the user's cursor to that location.
        /// </summary>
        public virtual void ViewDefaultEvent(IComponent component)
        {
            EventDescriptor defaultEvent = TypeDescriptor.GetDefaultEvent(component);
            PropertyDescriptor defaultPropEvent = null;
            bool eventChanged = false;
            IEventBindingService eps = (IEventBindingService)GetService(typeof(IEventBindingService));
            if (eps is not null)
            {
                defaultPropEvent = eps.GetEventProperty(defaultEvent);
            }

            // If we couldn't find a property for this event, or if the property is read only, then abort and just show the code.
            if (defaultPropEvent is null || defaultPropEvent.IsReadOnly)
            {
                eps?.ShowCode();
                return;
            }

            string handler = (string)defaultPropEvent.GetValue(component);

            // If there is no handler set, set one now.
            if (handler is null)
            {
                eventChanged = true;
                handler = eps.CreateUniqueMethodName(component, defaultEvent);
            }

            IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
            DesignerTransaction trans = null;

            try
            {
                if (host is not null)
                {
                    trans = host.CreateTransaction(string.Format(SR.WindowsFormsAddEvent, defaultEvent.Name));
                }

                // Save the new value... BEFORE navigating to it!
                if (eventChanged && defaultPropEvent is not null)
                {
                    defaultPropEvent.SetValue(component, handler);
                }

                eps.ShowCode(component, defaultEvent);
            }
            finally
            {
                trans?.Commit();
            }
        }

        /// <summary>
        ///  This method should be called by the extending designer for each message the control would normally receive.
        ///  This allows the designer to pre-process messages before allowing them to be routed to the control.
        /// </summary>
        protected override void WndProc(ref Message m)
        {
            switch (m.MsgInternal)
            {
                case PInvokeCore.WM_SETCURSOR:
                    // We always handle setting the cursor ourselves.
                    OnSetCursor();
                    break;
                case PInvokeCore.WM_CONTEXTMENU:
                    // We must handle this ourselves. Control only allows regular Windows Forms context menus, which
                    // doesn't do us much good. Also, control's button up processing calls DefwndProc first, which
                    // causes a right mouse up to be routed as a WM_CONTEXTMENU. If we don't respond to it here,
                    // this message will be bubbled up to our parent, which would pop up a container context menu
                    // instead of our own.

                    Point location = PARAM.ToPoint(m.LParamInternal);
                    if (location.X == -1 && location.Y == -1)
                    {
                        // for shift-F10
                        location = MousePosition;
                    }

                    OnContextMenu(location);
                    break;
                case PInvokeCore.WM_NCHITTEST:
                    if (_tray._glyphManager is not null)
                    {
                        // Make sure that we send our glyphs hit test messages over the TrayControls too.
                        Point pt = PARAM.ToPoint(m.LParamInternal);
                        Point pt1 = PointToClient(default);
                        pt.Offset(pt1.X, pt1.Y);

                        // Offset the location of the traycontrol so we're in component tray coordinates.
                        pt.Offset(Location.X, Location.Y);
                        _tray._glyphManager.GetHitTest(pt);
                    }

                    base.WndProc(ref m);
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

        private class TrayControlAccessibleObject : ControlAccessibleObject
        {
            private readonly ComponentTray _tray;
            public TrayControlAccessibleObject(TrayControl owner, ComponentTray tray) : base(owner)
            {
                _tray = tray;
            }

            private IComponent Component
            {
                get => ((TrayControl)Owner).Component;
            }

            public override AccessibleStates State
            {
                get
                {
                    AccessibleStates state = base.State;
                    ISelectionService s = (ISelectionService)_tray.GetService(typeof(ISelectionService));
                    if (s is not null)
                    {
                        if (s.GetComponentSelected(Component))
                        {
                            state |= AccessibleStates.Selected;
                        }

                        if (s.PrimarySelection == Component)
                        {
                            state |= AccessibleStates.Focused;
                        }
                    }

                    return state;
                }
            }
        }
    }

    private class ComponentTrayGlyphManager
    {
        private Adorner _traySelectionAdorner; // we'll use a single adorner to manage the glyphs
        private Glyph _hitTestedGlyph; // the last glyph we hit tested (can be null)
        private readonly BehaviorService _behaviorSvc;

        /// <summary>
        ///  Constructor that simply creates an empty adorner.
        /// </summary>
        public ComponentTrayGlyphManager(BehaviorService behaviorSvc)
        {
            _behaviorSvc = behaviorSvc;
            _traySelectionAdorner = new Adorner();
        }

        /// <summary>
        ///  This is how we publically expose our glyph collection so that other designer services can 'add value'.
        /// </summary>
        public GlyphCollection SelectionGlyphs
        {
            get => _traySelectionAdorner.Glyphs;
        }

        /// <summary>
        ///  Clears the adorner of glyphs.
        /// </summary>
        public void Dispose()
        {
            _traySelectionAdorner?.Glyphs.Clear();
            _traySelectionAdorner = null;
        }

        /// <summary>
        ///  Retrieves a list of glyphs associated with the component.
        /// </summary>
        public GlyphCollection GetGlyphsForComponent(IComponent comp)
        {
            GlyphCollection glyphs = [];
            if (_behaviorSvc is not null && comp is not null)
            {
                if (_behaviorSvc.DesignerActionUI is not null)
                {
                    Glyph g = _behaviorSvc.DesignerActionUI.GetDesignerActionGlyph(comp);
                    if (g is not null)
                    {
                        glyphs.Add(g);
                    }
                }
            }

            return glyphs;
        }

        /// <summary>
        ///  Called from the tray's NCHITTEST message in the WndProc.
        ///  We use this to loop through our glyphs and identify which one is successfully hit tested.
        ///  From here, we know where to send our messages.
        /// </summary>
        public Cursor GetHitTest(Point p)
        {
            for (int i = 0; i < _traySelectionAdorner.Glyphs.Count; i++)
            {
                Cursor hitTestCursor = _traySelectionAdorner.Glyphs[i].GetHitTest(p);
                if (hitTestCursor is not null)
                {
                    _hitTestedGlyph = _traySelectionAdorner.Glyphs[i];
                    return hitTestCursor;
                }
            }

            _hitTestedGlyph = null;
            return null;
        }

        /// <summary>
        ///  Called when the tray receives this mouse message.
        ///  Here, we'll give our glyphs the first chance to respond to the message before the tray even sees it.
        /// </summary>
        public bool OnMouseDoubleClick(MouseEventArgs e)
        {
            if (_hitTestedGlyph is not null && _hitTestedGlyph.Behavior is not null)
            {
                return _hitTestedGlyph.Behavior.OnMouseDoubleClick(_hitTestedGlyph, e.Button, new Point(e.X, e.Y));
            }

            return false;
        }

        /// <summary>
        ///  Called when the tray receives this mouse message.
        ///  Here, we'll give our glyphs the first chance to respond to the message before the tray even sees it.
        /// </summary>
        public bool OnMouseDown(MouseEventArgs e)
        {
            if (_hitTestedGlyph is not null && _hitTestedGlyph.Behavior is not null)
            {
                return _hitTestedGlyph.Behavior.OnMouseDown(_hitTestedGlyph, e.Button, new Point(e.X, e.Y));
            }

            return false;
        }

        /// <summary>
        ///  Called when the tray receives this mouse message.
        ///  Here, we'll give our glyphs the first chance to respond to the message before the tray even sees it.
        /// </summary>
        public bool OnMouseMove(MouseEventArgs e)
        {
            if (_hitTestedGlyph is not null && _hitTestedGlyph.Behavior is not null)
            {
                return _hitTestedGlyph.Behavior.OnMouseMove(_hitTestedGlyph, e.Button, new Point(e.X, e.Y));
            }

            return false;
        }

        /// <summary>
        ///  Called when the tray receives this mouse message.
        ///  Here, we'll give our glyphs the first chance to respond to the message before the tray even sees it.
        /// </summary>
        public bool OnMouseUp(MouseEventArgs e)
        {
            if (_hitTestedGlyph is not null && _hitTestedGlyph.Behavior is not null)
            {
                return _hitTestedGlyph.Behavior.OnMouseUp(_hitTestedGlyph, e.Button);
            }

            return false;
        }

        /// <summary>
        ///  Called when the comp tray or any tray control paints.
        ///  This will simply enumerate through the glyphs in our Adorner and ask them to paint.
        /// </summary>
        public void OnPaintGlyphs(PaintEventArgs pe)
        {
            // Paint any glyphs our tray adorner has
            foreach (Glyph g in _traySelectionAdorner.Glyphs)
            {
                g.Paint(pe);
            }
        }

        /// <summary>
        ///  Called when a tray control's location has changed.
        ///  We'll loop through our glyphs and invalidate any that are associated with the component.
        /// </summary>
        public void UpdateLocation(TrayControl trayControl)
        {
            foreach (Glyph g in _traySelectionAdorner.Glyphs)
            {
                // only look at glyphs that derive from designerglyph base (actions)
                if (g is DesignerActionGlyph desGlyph && ((DesignerActionBehavior)(desGlyph.Behavior)).RelatedComponent.Equals(trayControl.Component))
                {
                    desGlyph.UpdateAlternativeBounds(trayControl.Bounds);
                }
            }
        }
    }

    internal class AutoArrangeComparer : IComparer<Control>
    {
        int IComparer<Control>.Compare(Control o1, Control o2)
        {
            Debug.Assert(o1 is not null && o2 is not null, "Null objects sent for comparison!!!");
            Point tcLoc1 = o1.Location;
            Point tcLoc2 = o2.Location;
            int height = o1.Height / 2;
            // If they are at the same location, they are equal.
            if (tcLoc1.X == tcLoc2.X && tcLoc1.Y == tcLoc2.Y)
            {
                return 0;
            }

            // Is the first control lower than the 2nd...
            if (tcLoc1.Y + height <= tcLoc2.Y)
            {
                return -1;
            }

            // Is the 2nd control lower than the first...
            if (tcLoc2.Y + height <= tcLoc1.Y)
            {
                return 1;
            }

            // Which control is left of the other...
            return ((tcLoc1.X <= tcLoc2.X) ? -1 : 1);
        }
    }

    private class TraySelectionUIHandler : SelectionUIHandler
    {
        private readonly ComponentTray _tray;
        private Size _snapSize = Size.Empty;

        /// <summary>
        ///  Creates a new selection UI handler for the given component tray.
        /// </summary>
        public TraySelectionUIHandler(ComponentTray tray)
        {
            _tray = tray;
            _snapSize = default;
        }

        /// <summary>
        ///  Called when the user has started the drag.
        /// </summary>
        public override bool BeginDrag(object[] components, SelectionRules rules, int initialX, int initialY)
        {
            bool value = base.BeginDrag(components, rules, initialX, initialY);
            _tray.SuspendLayout();
            return value;
        }

        /// <summary>
        ///  Called when the user has completed the drag. The designer should remove any UI feedback it may be providing.
        /// </summary>
        public override void EndDrag(object[] components, bool cancel)
        {
            base.EndDrag(components, cancel);
            _tray.ResumeLayout();
        }

        /// <summary>
        ///  Retrieves the base component for the selection handler.
        /// </summary>
        protected override IComponent GetComponent()
        {
            return _tray;
        }

        /// <summary>
        ///  Retrieves the base component's UI control for the selection handler.
        /// </summary>
        protected override Control GetControl()
        {
            return _tray;
        }

        /// <summary>
        ///  Retrieves the UI control for the given component.
        /// </summary>
        protected override Control GetControl(IComponent component)
        {
            return TrayControl.FromComponent(component);
        }

        /// <summary>
        ///  Retrieves the current grid snap size we should snap objects to.
        /// </summary>
        protected override Size GetCurrentSnapSize()
        {
            return _snapSize;
        }

        /// <summary>
        ///  We use this to request often-used services.
        /// </summary>
        protected override object GetService(Type serviceType)
        {
            return _tray.GetService(serviceType);
        }

        /// <summary>
        ///  Determines if the selection UI handler should attempt to snap objects to a grid.
        /// </summary>
        protected override bool GetShouldSnapToGrid()
        {
            return false;
        }

        /// <summary>
        ///  Given a rectangle, this updates the dimensions of it with any grid snaps and returns a new rectangle.
        ///  If no changes to the rectangle's size were needed, this may return the same rectangle.
        /// </summary>
        public override Rectangle GetUpdatedRect(Rectangle originalRect, Rectangle dragRect, bool updateSize)
        {
            return dragRect;
        }

        /// <summary>
        ///  Asks the handler to set the appropriate cursor
        /// </summary>
        public override void SetCursor()
        {
            _tray.OnSetCursor();
        }
    }

    private class TrayOleDragDropHandler : OleDragDropHandler
    {
        public TrayOleDragDropHandler(SelectionUIHandler selectionHandler, IServiceProvider serviceProvider, IOleDragClient client) : base(selectionHandler, serviceProvider, client)
        {
        }

        protected override bool CanDropDataObject(IDataObject dataObj)
        {
            ICollection comps = null;
            if (dataObj is not null)
            {
                if (dataObj is ComponentDataObjectWrapper cdow)
                {
                    ComponentDataObject cdo = cdow.InnerData;
                    comps = cdo.Components;
                }
                else
                {
                    try
                    {
                        object serializationData = dataObj.GetData(DataFormat, true);
                        if (serializationData is null)
                        {
                            return false;
                        }

                        IDesignerSerializationService ds = (IDesignerSerializationService)GetService(typeof(IDesignerSerializationService));
                        if (ds is null)
                        {
                            return false;
                        }

                        comps = ds.Deserialize(serializationData);
                    }
                    catch (Exception e) when (!e.IsCriticalException())
                    {
                        // We return false on any exception.
                    }
                }
            }

            if (comps is not null && comps.Count > 0)
            {
                foreach (object comp in comps)
                {
                    if (comp is Point)
                    {
                        continue;
                    }

                    if (comp is Control or not IComponent)
                    {
                        return false;
                    }
                }

                return true;
            }

            return false;
        }
    }
}
